
/* GCSx
** LAYER.CPP
**
** Layer storage format and layer-related functions
** Doesn't include any editor-only functionality
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

Layer::Layer(World* myWorld, Scene* myScene, int myId) : name(blankString), nameL(blankString), spawns() { start_func
    if (myId) assert(myWorld);
    assert(myScene);
    
    world = myWorld;
    scene = myScene;
    cached = 0;
    layerType = LAYER_EMPTY;
    xSize = ySize = LAYER_TILE_DEFAULT_SIZE;
    tilesetId = 0;
    tileset = NULL;
    tileData = NULL;
    effectsData = NULL;
    extendedData = NULL;
    usesEffects = 0;
    usesExtended = 0;
    lockCount = 0;
    lockCountPlay = 0;
    id = myId;

    assert((int)LAYER_TILE_FLIP == (int)TextureMap::TEXTURE_FLIP);
    assert((int)LAYER_TILE_MIRROR == (int)TextureMap::TEXTURE_MIRROR);
    assert((int)LAYER_TILE_ROTATE == (int)TextureMap::TEXTURE_ROTATE);
}

Layer::~Layer() { start_func
    if (tileset) {
        for (int pos = 0; pos < lockCount; ++pos) {
            tileset->markUnlock();
        }
        for (int pos = 0; pos < lockCountPlay; ++pos) {
            tileset->markUnlockPlay();
        }
    }

    delete[] tileData;
    delete[] effectsData;
    delete[] extendedData;

    vector<Spawn*>::iterator end = spawns.end();
    for (vector<Spawn*>::iterator pos = spawns.begin(); pos != end; ++pos) {
        assert(*pos);
        delete *pos;
    }
}

int Layer::getXSize() const { start_func
    // @TODO: Valid for image/font layers, but shouldn't be called at this time
    assert(layerType == LAYER_TILE);

    return xSize;
}

int Layer::getYSize() const { start_func
    // @TODO: Valid for image/font layers, but shouldn't be called at this time
    assert(layerType == LAYER_TILE);

    return ySize;
}

void Layer::loadHeaderNonSpawns(FileRead* file) throw_File { start_func
    assert(file);
    assert(world);
    
    if (file->getVersion() > 1) {
        throw FileException("Unsupported layer version %d", file->getVersion());
    }

    file->readStr(name);
    nameL = name;
    toLower(nameL);
    layerType = (LayerType)file->readInt();
    id = file->readInt();
    
    switch (layerType) {
        case LAYER_EMPTY:
            break;
            
        case LAYER_TILE:
            xSize = file->readInt();
            ySize = file->readInt();
            tilesetId = file->readInt();
            usesExtended = file->readInt();
            usesEffects = file->readInt();
            tileset = world->findTileSet(tilesetId);
            
            if ((xSize > MAX_LAYERTILESIZE) || (xSize < 0) ||
                (ySize > MAX_LAYERTILESIZE) || (ySize < 0) ||
                (usesEffects < 0) || (usesEffects > 1) ||
                (usesExtended < 0) || (usesExtended > 1) ||
                (!tileset) || (!id)) {
                throw FileException("Corrupted layer header");
            }

            break;

        case LAYER_IMAGE:
        case LAYER_FONT:
            // @TODO: error checking
            xSize = file->readInt();
            ySize = file->readInt();
            tilesetId = 0;
            usesEffects = 0;
            usesExtended = 0;
            tileset = NULL;
            break;
        
        default:
            throw FileException("Corrupted layer header");
    }
}

void Layer::loadHeader(FileRead* file) throw_File { start_func
    loadHeaderNonSpawns(file);

    int numSpawns = file->readInt();
    if (numSpawns < 0) throw FileException("Corrupted layer header");
    assert(spawns.empty());
    for (; numSpawns > 0; --numSpawns) {
        Spawn* newSpawn = new Spawn();
        try {
            newSpawn->load(file, world);
        }
        catch (...) {
            delete newSpawn;
            throw;
        }
        spawns.push_back(newSpawn);
    }
    
    cached = 1;
}

void Layer::loadContent(FileRead* file) throw_File { start_func
    // We don't delete 'file' if we throw because we're only
    // supposed to be called this from Scene
    assert(file);
    assert(file->getVersion() <= 1);
    
    switch (layerType) {
        case LAYER_EMPTY:
        case LAYER_IMAGE: // @TODO:
        case LAYER_FONT: // @TODO:
            break;
            
        case LAYER_TILE:
            assert(!tileData);
            assert(!effectsData);
            assert(!extendedData);
            
            tileData = new Uint32[xSize * ySize];
            file->readIntBulk(tileData, xSize * ySize);
            if (usesExtended) {
                extendedData = new Uint32[xSize * ySize];
                file->readIntBulk(extendedData, xSize * ySize);
            }
            if (usesEffects) {
                effectsData = new Uint32[xSize * ySize];
                file->readIntBulk(effectsData, xSize * ySize);
            }
            break;
        
        default:
            assert(0);
    }
    
    cached = 0;
}

void Layer::cacheLoad() { start_func
    // Should never be called
    assert(0);
}

void Layer::recacheContent() { start_func
    assert(!cached);
    assert(!lockCount && !lockCountPlay);
    
    delete[] tileData;
    tileData = NULL;
    delete[] effectsData;
    effectsData = NULL;
    delete[] extendedData;
    extendedData = NULL;
    
    cached = 1;
}

int Layer::doLock(int play) throw_File { start_func
    switch (layerType) {
        case LAYER_EMPTY:
        case LAYER_IMAGE:
        case LAYER_FONT: // @TODO:
            break;
            
        case LAYER_TILE:
            if (tileset) {
                if (play) tileset->markLockPlay();
                else tileset->markLock();
            }
            break;
        
        default:
            assert(0);
    }
    
    if ((!lockCount) && (!lockCountPlay)) {
        SpriteOrder::iterator end = spritesByOrder.end();
        for (SpriteOrder::iterator pos = spritesByOrder.begin(); pos != end; ++pos) {
            (*pos)->setActive();
        }
    }

    if (play) ++lockCountPlay;
    else ++lockCount;
    
    return lockCount + lockCountPlay;
}

int Layer::doUnlock(int play) { start_func
    if (play) --lockCountPlay;
    else --lockCount;
    
    assert(lockCount >= 0);
    assert(lockCountPlay >= 0);

    if ((!lockCount) && (!lockCountPlay)) {
        SpriteOrder::iterator end = spritesByOrder.end();
        for (SpriteOrder::iterator pos = spritesByOrder.begin(); pos != end; ++pos) {
            (*pos)->setInactive();
        }
    }

    switch (layerType) {
        case LAYER_EMPTY:
        case LAYER_IMAGE:
        case LAYER_FONT: // @TODO:
            break;
            
        case LAYER_TILE:
            if (tileset) {
                if (play) tileset->markUnlockPlay();
                else tileset->markUnlock();
            }
            break;
        
        default:
            assert(0);
    }

    return lockCount + lockCountPlay;
}

int Layer::markLock() throw_File { start_func
    return doLock(0);
}

int Layer::markUnlock() { start_func
    return doUnlock(0);
}

int Layer::markLockPlay() throw_File { start_func
    return doLock(1);
}

int Layer::markUnlockPlay() { start_func
    return doUnlock(1);
}

int Layer::isContentCached() const { start_func
    return cached;
}

int Layer::isLocked() const { start_func
    return lockCount + lockCountPlay;
}

void Layer::draw(int viewX, int viewY) { start_func
    assert(lockCountPlay > 0);
    
    switch (layerType) {
        case LAYER_EMPTY:
        case LAYER_IMAGE:
        case LAYER_FONT: // @TODO:
            break;

        case LAYER_TILE:
            drawTile(viewX, viewY);
            break;

        default:
            assert(0);
    }

    drawSprites(viewX, viewY);
}

void Layer::drawSprites(int viewX, int viewY) { start_func
    SpriteOrder::iterator end = spritesByOrder.end();
    for (SpriteOrder::iterator pos = spritesByOrder.begin(); pos != end; ++pos) {
        (*pos)->draw(viewX, viewY);
    }
}

void Layer::drawTile(int viewX, int viewY) { start_func
    if (tileset) {
        const TextureMap* texm = tileset->getTexture();
        assert(texm);
        
        int tw = tileset->getWidth();
        int th = tileset->getHeight();
        int tc = tileset->getCount();
        int wrap = 1;
        int startX, y, startXTile, startYTile, maxX, maxY, pitch;
        int lastColor = -1;
        int lastAlpha = -1;

        // @TODO: possible optimization knowing entire layer is one color/alpha/orientation

        if (wrap) {
            // Tile in upper-left corner, or nearest-to but starting offscreen
            startXTile = (viewX - ((viewX < 0) ? tw - 1 : 0)) / tw;
            startYTile = (viewY - ((viewY < 0) ? th - 1 : 0)) / th;
            
            // Pixel coordinate of that tile
            startX = startXTile * tw - viewX;
            y = startYTile * th - viewY;

            // Adjust to correct wrapped tile
            startXTile = startXTile % xSize;
            if (startXTile < 0) startXTile += xSize;
            startYTile = startYTile % ySize;
            if (startYTile < 0) startYTile += ySize;

            // Farthest right/down *pixel* we draw
            // This is an inner bound, not an outer- we draw this pixel, but not the next
            maxX = screenWidth - 1;
            maxY = screenHeight - 1;

            // End-of-line pitch
            pitch = (maxX - startX + tw) / tw;
            pitch = pitch % xSize;
            pitch = xSize - pitch;
            if (pitch <= startXTile) pitch += xSize;
        }
        else {
            // True starting point, based on viewpoint
            startX = -viewX;
            y = -viewY;

            // Which tile is closest to upper left corner
            startXTile = -(startX / tw);
            startYTile = -(y / th);

            // Behavior at edge
            if (startXTile < 0) startXTile = 0;
            if (startYTile < 0) startYTile = 0;

            // Adjust starting point to closest tile to upper left
            startX += startXTile * tw;
            y += startYTile * th;

            // Farthest right/down *pixel* we draw- bounded to size
            // This is an inner bound, not an outer- we draw this pixel, but not the next
            maxX = min(screenWidth - 1, startX + tw * (xSize - startXTile) - 1);
            maxY = min(screenHeight - 1, y + th * (ySize - startYTile) - 1);
            
            // (skip display if nothing to show)
            if ((maxX < 0) || (maxY < 0) ||
                (startX > maxX) || (y > maxY)) return;

            // End-of-line pitch
            pitch = (maxX - startX + tw) / tw;
            pitch = xSize - pitch;
        }

        // Starting point
        Uint32* tiles = tileData + startXTile + startYTile * xSize;
        int x = startX;
        // (these only matter if wrapping)
        int xt = startXTile;
        int yt = startYTile;

        // Extended data
        Uint32* ext = NULL;
        if (usesExtended) ext = extendedData + startXTile + startYTile * xSize;

        // Optimize loop based on possible settings
        // Compiler will optimize-out clauses/conditionals this way
#define drawLayerLoop(doWrap, doExt) \
        while (1) { \
            int data = *tiles++; \
            int alpha; \
            if (ext) alpha = *ext++; \
            int tile = data & LAYER_TILE_INDEX; \
            if ((tile) && (tile <= tc)) { \
                int color = data & LAYER_TILE_COLOR; \
                if ((color != lastColor) || ((ext) && (alpha != lastAlpha))) { \
                    lastColor = color; \
                    if (ext) { \
                        lastAlpha = alpha; \
                        glColor4f((color & LAYER_TILE_COLOR_R) / (float)LAYER_TILE_COLOR_R, \
                                  (color & LAYER_TILE_COLOR_G) / (float)LAYER_TILE_COLOR_G, \
                                  (color & LAYER_TILE_COLOR_B) / (float)LAYER_TILE_COLOR_B, \
                                  alpha / (float)LAYER_EXT_ALPHA \
                                  ); \
                    } \
                    else { \
                        glColor3f((color & LAYER_TILE_COLOR_R) / (float)LAYER_TILE_COLOR_R, \
                                  (color & LAYER_TILE_COLOR_G) / (float)LAYER_TILE_COLOR_G, \
                                  (color & LAYER_TILE_COLOR_B) / (float)LAYER_TILE_COLOR_B \
                                  ); \
                    } \
                } \
                texm->draw(tile, x, y, data & LAYER_TILE_ORIENTATION); \
            } \
            x += tw; \
            if (doWrap) { \
                xt = (xt + 1) % xSize; \
                if (xt == 0) { \
                    tiles -= xSize; \
                    if (ext) ext -= xSize; \
                } \
            } \
            if (x > maxX) { \
                y += th; \
                if (y > maxY) break; \
                x = startX; \
                tiles += pitch; \
                if (ext) ext += pitch; \
                if (doWrap) { \
                    xt = startXTile; \
                    yt = (yt + 1) % ySize; \
                    if (yt == 0) { \
                        tiles -= ySize * xSize; \
                        if (ext) ext -= ySize * xSize; \
                    } \
                } \
            } \
        }

        if (wrap) {
            if (usesExtended) {
                drawLayerLoop(1, 1);
            }
            else {
                drawLayerLoop(1, 0);
            }
        }
        else {
            if (usesExtended) {
                drawLayerLoop(0, 1);
            }
            else {
                drawLayerLoop(0, 0);
            }
        }
    }
}

void Layer::spawnLayer() { start_func
    vector<Spawn*>::iterator end = spawns.end();
    WorldPlay* wp = dynamic_cast<WorldPlay*>(world);
    assert(wp);
    for (vector<Spawn*>::iterator pos = spawns.begin(); pos != end; ++pos) {
        assert(*pos);
        (*pos)->generate(this, wp);
    }
}

void Layer::indexSprite(Sprite* addSprite) { start_func
    assert(addSprite);
    WorldPlay* wp = dynamic_cast<WorldPlay*>(world);
    assert(wp);
    
    wp->indexSprite(addSprite);
    orderSprite(addSprite);
    if (lockCount || lockCountPlay) addSprite->setActive();
}

void Layer::deindexSprite(Sprite* remSprite) { start_func
    assert(remSprite);
    WorldPlay* wp = dynamic_cast<WorldPlay*>(world);
    assert(wp);
    
    if (lockCount || lockCountPlay) remSprite->setInactive();
    wp->deindexSprite(remSprite);
    deorderSprite(remSprite);
}

void Layer::orderSprite(Sprite* addSprite) { start_func
    assert(addSprite);
    assert(spritesByOrder.find(addSprite) == spritesByOrder.end());

    spritesByOrder.insert(addSprite);
}

void Layer::deorderSprite(Sprite* remSprite) { start_func
    assert(remSprite);
    spritesByOrder.erase(remSprite);
}
